//IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.

// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.


//                          License Agreement
//               For Open Source Computer Vision Library

//Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
//Copyright (C) 2008-2010, Willow Garage Inc., all rights reserved.
//Third party copyrights are property of their respective owners.

//Redistribution and use in source and binary forms, with or without modification,
//are permitted provided that the following conditions are met:

//  * Redistribution's of source code must retain the above copyright notice,
//  this list of conditions and the following disclaimer.

//  * Redistribution's in binary form must reproduce the above copyright notice,
//  this list of conditions and the following disclaimer in the documentation
//  and/or other materials provided with the distribution.

//  * The name of the copyright holders may not be used to endorse or promote products
//  derived from this software without specific prior written permission.

//This software is provided by the copyright holders and contributors "as is" and
//any express or implied warranties, including, but not limited to, the implied
//warranties of merchantability and fitness for a particular purpose are disclaimed.
//In no event shall the Intel Corporation or contributors be liable for any direct,
//indirect, incidental, special, exemplary, or consequential damages
//(including, but not limited to, procurement of substitute goods or services;
//loss of use, data, or profits; or business interruption) however caused
//and on any theory of liability, whether in contract, strict liability,
//or tort (including negligence or otherwise) arising in any way out of
//the use of this software, even if advised of the possibility of such damage.

//--------------------Google Code 2010 -- Yannick Verdie--------------------//
#ifndef __BM_HIGHGUI_QT_H__
#define __BM_HIGHGUI_QT_H__

#include "opencv2/opencv.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/highgui/highgui_c.h"

#ifndef _DEBUG
#define QT_NO_DEBUG_OUTPUT
#endif

#if defined( HAVE_QT_OPENGL )
#include <QtOpenGL>
#include <QGLWidget>
#endif

#include <QAbstractEventDispatcher>
#include <QApplication>
#include <QFile>
#include <QPushButton>
#include <QGraphicsView>
#include <QSizePolicy>
#include <QInputDialog>
#include <QBoxLayout>
#include <QSettings>
#include <qtimer.h>
#include <QtConcurrentRun>
#include <QWaitCondition>
#include <QKeyEvent>
#include <QMetaObject>
#include <QPointer>
#include <QSlider>
#include <QLabel>
#include <QIODevice>
#include <QShortcut>
#include <QStatusBar>
#include <QVarLengthArray>
#include <QFileInfo>
#include <QDate>
#include <QFileDialog>
#include <QToolBar>

#include <QAction>
#include <QCheckBox>
#include <QRadioButton>
#include <QButtonGroup>
#include <QMenu>
#include <QTest>

//start private enum
enum { CV_MODE_NORMAL = 0, CV_MODE_OPENGL = 1 };

//we can change the keyboard shortcuts from here !
enum {	shortcut_zoom_normal 	= Qt::CTRL + Qt::Key_Z,
        shortcut_zoom_imgRegion = Qt::CTRL + Qt::Key_X,
        shortcut_save_img		= Qt::CTRL + Qt::Key_S,
        shortcut_properties_win	= Qt::CTRL + Qt::Key_P,
        shortcut_zoom_in 		= Qt::CTRL + Qt::Key_Plus,//QKeySequence(QKeySequence::ZoomIn),
        shortcut_zoom_out		= Qt::CTRL + Qt::Key_Minus,//QKeySequence(QKeySequence::ZoomOut),
        shortcut_panning_left 	= Qt::CTRL + Qt::Key_Left,
        shortcut_panning_right 	= Qt::CTRL + Qt::Key_Right,
        shortcut_panning_up 	= Qt::CTRL + Qt::Key_Up,
        shortcut_panning_down 	= Qt::CTRL + Qt::Key_Down
    };
//end enum

inline void convertToShow(const cv::Mat &src, cv::Mat &dst, bool toRGB = true)
{
    const int src_depth = src.depth();
    CV_Assert(src_depth != CV_16F && src_depth != CV_32S);
    cv::Mat tmp;
    switch(src_depth)
    {
        case CV_8U:
            tmp = src;
            break;
        case CV_8S:
            cv::convertScaleAbs(src, tmp, 1, 127);
            break;
        case CV_16S:
            cv::convertScaleAbs(src, tmp, 1/255., 127);
            break;
        case CV_16U:
            cv::convertScaleAbs(src, tmp, 1/255.);
            break;
        case CV_32F:
        case CV_64F: // assuming image has values in range [0, 1)
            src.convertTo(tmp, CV_8U, 255., 0.);
            break;
    }
    cv::cvtColor(tmp, dst, toRGB ? cv::COLOR_BGR2RGB : cv::COLOR_BGRA2BGR, dst.channels());
}

inline void convertToShow(const cv::Mat &src, const CvMat* arr, bool toRGB = true)
{
    cv::Mat dst = cv::cvarrToMat(arr);
    convertToShow(src, dst, toRGB);
    CV_Assert(dst.data == arr->data.ptr);
}

namespace bm {
    class CvWindow;

    class ViewPort;


    class GuiReceiver : public QObject {
    Q_OBJECT

    public:
        GuiReceiver();

        ~GuiReceiver();

        int start();

        void isLastWindow();

        bool bTimeOut;
        QTimer *timer;

    public slots:

        void createWindow(QString name, int flags = 0);

        void destroyWindow(QString name);

        void destroyAllWindow();

        void addSlider(QString trackbar_name, QString window_name, void *value, int count, void *on_change);

        void
        addSlider2(QString trackbar_name, QString window_name, void *value, int count, void *on_change, void *userdata);

        void moveWindow(QString name, int x, int y);

        void resizeWindow(QString name, int width, int height);

        void showImage(QString name, void *arr);

        void displayInfo(QString name, QString text, int delayms);

        void displayStatusBar(QString name, QString text, int delayms);

        void timeOut();

        void toggleFullScreen(QString name, double flags);

        CvRect getWindowRect(QString name);

        double isFullScreen(QString name);

        double getPropWindow(QString name);

        void setPropWindow(QString name, double flags);

        void setWindowTitle(QString name, QString title);

        double getWindowVisible(QString name);

        double getRatioWindow(QString name);

        void setRatioWindow(QString name, double arg2);

        void saveWindowParameters(QString name);

        void loadWindowParameters(QString name);

        void putText(void *arg1, QString text, QPoint org, void *font);

        void addButton(QString button_name, int button_type, int initial_button_state, void *on_change, void *userdata);

        void enablePropertiesButtonEachWindow();

        void setOpenGlDrawCallback(QString name, void *callback, void *userdata);

        void setOpenGlContext(QString name);

        void updateWindow(QString name);

        double isOpenGl(QString name);

    private:
        int nb_windows;
        bool doesExternalQAppExist;
    };


    enum typeBar {
        type_CvTrackbar = 0, type_CvButtonbar = 1
    };

    class CvBar : public QHBoxLayout {
    public:
        typeBar type;
        QString name_bar;
        QPointer<QWidget> myparent;
    };


    class CvButtonbar : public CvBar {
    Q_OBJECT
    public:
        CvButtonbar(QWidget *arg, QString bar_name);

        void addButton(QString button_name, CvButtonCallback call, void *userdata, int button_type,
                       int initial_button_state);

    private:
        void setLabel();

        QPointer<QLabel> label;
        QPointer<QButtonGroup> group_button;
    };


    class CvPushButton : public QPushButton {
    Q_OBJECT
    public:
        CvPushButton(CvButtonbar *par, QString button_name, CvButtonCallback call, void *userdata);

    private:
        CvButtonbar *myparent;
        QString button_name;
        CvButtonCallback callback;
        void *userdata;

    private slots:

        void callCallBack(bool);
    };


    class CvCheckBox : public QCheckBox {
    Q_OBJECT
    public:
        CvCheckBox(CvButtonbar *par, QString button_name, CvButtonCallback call, void *userdata,
                   int initial_button_state);

    private:
        CvButtonbar *myparent;
        QString button_name;
        CvButtonCallback callback;
        void *userdata;

    private slots:

        void callCallBack(bool);
    };


    class CvRadioButton : public QRadioButton {
    Q_OBJECT
    public:
        CvRadioButton(CvButtonbar *par, QString button_name, CvButtonCallback call, void *userdata,
                      int initial_button_state);

    private:
        CvButtonbar *myparent;
        QString button_name;
        CvButtonCallback callback;
        void *userdata;

    private slots:

        void callCallBack(bool);
    };


    class CvTrackbar : public CvBar {
    Q_OBJECT
    public:
        CvTrackbar(CvWindow *parent, QString name, int *value, int count, CvTrackbarCallback on_change);

        CvTrackbar(CvWindow *parent, QString name, int *value, int count, CvTrackbarCallback2 on_change, void *data);

        QPointer<QSlider> slider;

    private slots:

        void createDialog();

        void update(int myvalue);

    private:
        void setLabel(int myvalue);

        void create(CvWindow *arg, QString name, int *value, int count);

        QString createLabel();

        QPointer<QPushButton> label;
        CvTrackbarCallback callback;
        CvTrackbarCallback2 callback2;//look like it is use by python binding
        int *dataSlider;
        void *userdata;
    };

//Both are top level window, so that a way to differentiate them.
//if (obj->metaObject ()->className () == "CvWindow") does not give me robust result

    enum typeWindow {
        type_CvWindow = 1, type_CvWinProperties = 2
    };

    class CvWinModel : public QWidget {
    public:
        typeWindow type;
    };


    class CvWinProperties : public CvWinModel {
    Q_OBJECT
    public:
        CvWinProperties(QString name, QObject *parent);

        ~CvWinProperties();

        QPointer<QBoxLayout> myLayout;

    private:
        void closeEvent(QCloseEvent *e) CV_OVERRIDE;

        void showEvent(QShowEvent *event) CV_OVERRIDE;

        void hideEvent(QHideEvent *event) CV_OVERRIDE;
    };


    class CvWindow : public CvWinModel {
    Q_OBJECT
    public:
        CvWindow(QString arg2, int flag = CV_WINDOW_NORMAL);

        ~CvWindow();

        void setMouseCallBack(CvMouseCallback m, void *param);

        void writeSettings();

        void readSettings();

        double getRatio();

        void setRatio(int flags);

        CvRect getWindowRect();

        int getPropWindow();

        void setPropWindow(int flags);

        void toggleFullScreen(int flags);

        void updateImage(void *arr);

        void displayInfo(QString text, int delayms);

        void displayStatusBar(QString text, int delayms);

        void enablePropertiesButton();

        static CvButtonbar *createButtonBar(QString bar_name);

        static void
        addSlider(CvWindow *w, QString name, int *value, int count, CvTrackbarCallback on_change CV_DEFAULT(NULL));

        static void
        addSlider2(CvWindow *w, QString name, int *value, int count, CvTrackbarCallback2 on_change CV_DEFAULT(NULL),
                   void *userdata CV_DEFAULT(0));

        void setOpenGlDrawCallback(CvOpenGlDrawCallback callback, void *userdata);

        void makeCurrentOpenGlContext();

        void updateGl();

        bool isOpenGl();

        void setViewportSize(QSize size);

        //parameters (will be save/load)
        int param_flags;
        int param_gui_mode;
        int param_ratio_mode;

        QPointer<QBoxLayout> myGlobalLayout; //All the widget (toolbar, view, LayoutBar, ...) are attached to it
        QPointer<QBoxLayout> myBarLayout;

        QVector<QAction *> vect_QActions;

        QPointer<QStatusBar> myStatusBar;
        QPointer<QToolBar> myToolBar;
        QPointer<QLabel> myStatusBar_msg;

    protected:
        virtual void keyPressEvent(QKeyEvent *event) CV_OVERRIDE;

    private:

        int mode_display; //opengl or native
        ViewPort *myView;

        QVector<QShortcut *> vect_QShortcuts;

        void icvLoadTrackbars(QSettings *settings);

        void icvSaveTrackbars(QSettings *settings);

        void icvLoadControlPanel();

        void icvSaveControlPanel();

        void icvLoadButtonbar(CvButtonbar *t, QSettings *settings);

        void icvSaveButtonbar(CvButtonbar *t, QSettings *settings);

        void createActions();

        void createShortcuts();

        void createToolBar();

        void createView();

        void createStatusBar();

        void createGlobalLayout();

        void createBarLayout();

        CvWinProperties *createParameterWindow();

        void hideTools();

        void showTools();

        QSize getAvailableSize();

    private slots:

        void displayPropertiesWin();
    };


    enum type_mouse_event {
        mouse_up = 0, mouse_down = 1, mouse_dbclick = 2, mouse_move = 3, mouse_wheel = 4
    };
    static const int tableMouseButtons[][3] = {
            {CV_EVENT_LBUTTONUP,     CV_EVENT_RBUTTONUP,     CV_EVENT_MBUTTONUP},               //mouse_up
            {CV_EVENT_LBUTTONDOWN,   CV_EVENT_RBUTTONDOWN,   CV_EVENT_MBUTTONDOWN},         //mouse_down
            {CV_EVENT_LBUTTONDBLCLK, CV_EVENT_RBUTTONDBLCLK, CV_EVENT_MBUTTONDBLCLK},   //mouse_dbclick
            {CV_EVENT_MOUSEMOVE,     CV_EVENT_MOUSEMOVE,     CV_EVENT_MOUSEMOVE},               //mouse_move
            {0,                      0,                      0}                                                                   //mouse_wheel, to prevent exceptions in code
    };


    class ViewPort {
    public:
        virtual ~ViewPort() {}

        virtual QWidget *getWidget() = 0;

        virtual void setMouseCallBack(CvMouseCallback callback, void *param) = 0;

        virtual void writeSettings(QSettings &settings) = 0;

        virtual void readSettings(QSettings &settings) = 0;

        virtual double getRatio() = 0;

        virtual void setRatio(int flags) = 0;

        virtual void updateImage(const CvArr *arr) = 0;

        virtual void startDisplayInfo(QString text, int delayms) = 0;

        virtual void setOpenGlDrawCallback(CvOpenGlDrawCallback callback, void *userdata) = 0;

        virtual void makeCurrentOpenGlContext() = 0;

        virtual void updateGl() = 0;

        virtual void setSize(QSize size_) = 0;
    };


    class OCVViewPort : public ViewPort {
    public:
        explicit OCVViewPort();

        ~OCVViewPort() CV_OVERRIDE {};

        void setMouseCallBack(CvMouseCallback callback, void *param) CV_OVERRIDE;

    protected:
        void icvmouseEvent(QMouseEvent *event, type_mouse_event category);

        void icvmouseHandler(QMouseEvent *event, type_mouse_event category, int &cv_event, int &flags);

        virtual void icvmouseProcessing(QPointF pt, int cv_event, int flags);

        CvMouseCallback mouseCallback;
        void *mouseData;
    };


#ifdef HAVE_QT_OPENGL

    class OpenGlViewPort : public QGLWidget, public OCVViewPort
    {
    public:
        explicit OpenGlViewPort(QWidget* parent);
        ~OpenGlViewPort() CV_OVERRIDE;

        QWidget* getWidget() CV_OVERRIDE;

        void writeSettings(QSettings& settings) CV_OVERRIDE;
        void readSettings(QSettings& settings) CV_OVERRIDE;

        double getRatio() CV_OVERRIDE;
        void setRatio(int flags) CV_OVERRIDE;

        void updateImage(const CvArr* arr) CV_OVERRIDE;

        void startDisplayInfo(QString text, int delayms) CV_OVERRIDE;

        void setOpenGlDrawCallback(CvOpenGlDrawCallback callback, void* userdata) CV_OVERRIDE;
        void makeCurrentOpenGlContext() CV_OVERRIDE;
        void updateGl() CV_OVERRIDE;

        void setSize(QSize size_) CV_OVERRIDE;

    protected:
        void initializeGL() CV_OVERRIDE;
        void resizeGL(int w, int h) CV_OVERRIDE;
        void paintGL() CV_OVERRIDE;

        void wheelEvent(QWheelEvent* event) CV_OVERRIDE;
        void mouseMoveEvent(QMouseEvent* event) CV_OVERRIDE;
        void mousePressEvent(QMouseEvent* event) CV_OVERRIDE;
        void mouseReleaseEvent(QMouseEvent* event) CV_OVERRIDE;
        void mouseDoubleClickEvent(QMouseEvent* event) CV_OVERRIDE;

        QSize sizeHint() const CV_OVERRIDE;

    private:
        QSize size;

        CvOpenGlDrawCallback glDrawCallback;
        void* glDrawData;
    };

#endif // HAVE_QT_OPENGL


    class DefaultViewPort : public QGraphicsView, public OCVViewPort {
    Q_OBJECT

    public:
        DefaultViewPort(CvWindow *centralWidget, int arg2);

        ~DefaultViewPort() CV_OVERRIDE;

        QWidget *getWidget() CV_OVERRIDE;

        void writeSettings(QSettings &settings) CV_OVERRIDE;

        void readSettings(QSettings &settings) CV_OVERRIDE;

        double getRatio() CV_OVERRIDE;

        void setRatio(int flags) CV_OVERRIDE;

        void updateImage(const CvArr *arr) CV_OVERRIDE;

        void startDisplayInfo(QString text, int delayms) CV_OVERRIDE;

        void setOpenGlDrawCallback(CvOpenGlDrawCallback callback, void *userdata) CV_OVERRIDE;

        void makeCurrentOpenGlContext() CV_OVERRIDE;

        void updateGl() CV_OVERRIDE;

        void setSize(QSize size_) CV_OVERRIDE;

    public slots:
        //reference:
        //http://www.qtcentre.org/wiki/index.php?title=QGraphicsView:_Smooth_Panning_and_Zooming
        //http://doc.qt.nokia.com/4.6/gestures-imagegestures-imagewidget-cpp.html

        void siftWindowOnLeft();

        void siftWindowOnRight();

        void siftWindowOnUp();

        void siftWindowOnDown();

        void resetZoom();

        void imgRegion();

        void ZoomIn();

        void ZoomOut();

        void saveView();

    protected:
        void contextMenuEvent(QContextMenuEvent *event) CV_OVERRIDE;

        void resizeEvent(QResizeEvent *event) CV_OVERRIDE;

        void paintEvent(QPaintEvent *paintEventInfo) CV_OVERRIDE;

        void wheelEvent(QWheelEvent *event) CV_OVERRIDE;

        void mouseMoveEvent(QMouseEvent *event) CV_OVERRIDE;

        void mousePressEvent(QMouseEvent *event) CV_OVERRIDE;

        void mouseReleaseEvent(QMouseEvent *event) CV_OVERRIDE;

        void mouseDoubleClickEvent(QMouseEvent *event) CV_OVERRIDE;

    private:
        int param_keepRatio;

        //parameters (will be save/load)
        QTransform param_matrixWorld;

        CvMat *image2Draw_mat;
        QImage image2Draw_qt;
        int nbChannelOriginImage;


        void scaleView(qreal scaleFactor, QPointF center);

        void moveView(QPointF delta);

        QPoint mouseCoordinate;
        QPointF positionGrabbing;
        QRect positionCorners;
        QTransform matrixWorld_inv;
        float ratioX, ratioY;

        bool isSameSize(IplImage *img1, IplImage *img2);

        QSize sizeHint() const CV_OVERRIDE;

        QPointer<CvWindow> centralWidget;
        QPointer<QTimer> timerDisplay;
        bool drawInfo;
        QString infoText;
        QRectF target;

        void drawInstructions(QPainter *painter);

        void drawViewOverview(QPainter *painter);

        void drawImgRegion(QPainter *painter);

        void draw2D(QPainter *painter);

        void drawStatusBar();

        void controlImagePosition();

        void icvmouseProcessing(QPointF pt, int cv_event, int flags) CV_OVERRIDE;

    private slots:

        void stopDisplayInfo();
    };
}

#endif
